#pragma once

#include "Common.hpp"
#include "FixedLengthMemoryPool.hpp"
#include "PageMap.hpp"

class PageCache
{
public:
	static PageCache* GetInstance()
	{
		return &_sInst;
	}

	span* NewSpan(size_t k);
	span* MapObjectToSpan(void* obj); // 算对应地址对应的页号，然后通过映射找到对应的span
	void ReleaseSpanToPageCache(span* span);
	std::mutex _pageMtx;  // 一整把大锁，不采用桶锁。
private:
	SpanList _spanList[NPAGES];
private:
	PageCache()
	{}
	PageCache(const PageCache&) = delete;
	PageCache& operator=(const PageCache&) = delete;
	static PageCache _sInst;
	//std::unordered_map<span_t, span*> _pageToSpan;  // 做好span和对应页号的一个映射
	TCMalloc_PageMap1<32 - PAGE_SHIFT> _pageToSpan;
	FLMemoryPool<span> _spanPool;  // 定义一个span的定长内存申请池
};

PageCache PageCache::_sInst;  // 定义

span* PageCache::NewSpan(size_t k)
{
	//assert(k < NPAGES);
	// 申请多少页，返回对应的span对象
	
	if (k >= NPAGES)
	{
		// 如果超过128page，我们直接向堆进行申请
		void* ptr = SystemAlloc(k);
		//span* myspan = new span;
		span* myspan = _spanPool.New();
		myspan->_pageld = (span_t)ptr >> PAGE_SHIFT;
		myspan->_n = k;

		//pageToSpan[myspan->_pageld] = myspan;
		_pageToSpan.set(myspan->_pageld, myspan);
		return myspan;
	}

	// 直接定址法查看当前位置是否存在，存在的话直接返回，否则往后找进行切分
	if (!_spanList[k].Empty())
	{
		span* myspan = _spanList[k].PopFront();
		// 别忘了这里也存在缓存
		for (size_t i = 0; i < myspan->_n; ++i)
		{
			//pageToSpan[myspan->_pageld + i] = myspan;
			_pageToSpan.set(myspan->_pageld + i, myspan);
		}

		return myspan;
	}

	for (int i = int(k + 1); i < NPAGES; ++i)
	{
		if (!_spanList[i].Empty())
		{
			//span* aspan = new span;
			span* aspan = _spanPool.New();
			span* bspan = _spanList[i].PopFront();
			aspan->_pageld = bspan->_pageld;
			aspan->_n = k;
			bspan->_pageld += k;
			bspan->_n -= k;
			_spanList[bspan->_n].HeadInsert(bspan);

			// 此时切割的新的span为pagecache内，也需要首尾映射到map中，方便pagecache合并内存的时候寻找
			//pageToSpan[bspan->_pageld] = bspan;
			_pageToSpan.set(bspan->_pageld, bspan);
			//pageToSpan[bspan->_pageld + bspan->_n - 1] = bspan;
			_pageToSpan.set(bspan->_pageld + bspan->_n - 1, bspan);


			// 每返回一个新的aspan，我们需要将页号和span*进行一个关联，方便后续直接通过页号找到对应的span
			for (size_t i = 0; i < aspan->_n; ++i)
			{
				//pageToSpan[aspan->_pageld + i] = aspan;
				_pageToSpan.set(aspan->_pageld + i, aspan);
			}

			return aspan;
		}
	}

	// 此时说明page内不存在大块的内存，我们需要向系统进行申请
	void* page = nullptr;
	page = SystemAlloc(NPAGES - 1);  // 申请 128page 128 * 8 * 1024
	//span* _span = new span;
	span* _span = _spanPool.New();
	_span->_pageld = (span_t)page >> PAGE_SHIFT;  // 首先转化为字节单位，然后在变成page单位 转化为页号
	_span->_n = NPAGES - 1;
	_spanList[_span->_n].HeadInsert(_span);

	return NewSpan(k);  // 递归调用一次，复用上面的切割逻辑即可
}

span* PageCache::MapObjectToSpan(void* obj)
{
	// 传入的地址，算出对应的页号，然后通过哈希表找到对应span。按理说通过此接口一定能找到，找不到说明外面的逻辑存在缺陷
	span_t num = (span_t)obj >> PAGE_SHIFT;

	//std::unique_lock<std::mutex> mtx(_pageMtx);  // RAII 管理锁
	// 存在线程安全问题
	//auto it = pageToSpan.find(num);
	//if (it != pageToSpan.end())
	//{
	//	return it->second;
	//}
	span* it = (span*)_pageToSpan.get(num);
	if (it != nullptr)
	{
		return it;
	}
	else
	{
		assert(false);
		return nullptr;
	}
}

void PageCache::ReleaseSpanToPageCache(span* span)
{
	if (span->_n >= NPAGES)
	{
		void* ptr = (void*)(span->_pageld << PAGE_SHIFT);
		SystemFree(ptr);
		//delete span;
		_spanPool.Delete(span);
		return;
	}
	// 首先向前合并，然后向后合并
	while (true)
	{
		//auto cur = pageToSpan.find(span->_pageld - 1);
		//if (cur == pageToSpan.end()) break;
		::span* cur = (::span*)_pageToSpan.get(span->_pageld - 1);
		if (cur == nullptr) break;

		// 此时找到上一个了，但是需要考虑是否在使用
		::span* sprv = cur;
		if (sprv->_isUse) break;

		if (sprv->_n + span->_n > NPAGES - 1) break;  // 此时合成大小超过128page了，可以合并但是无法管理

		// 此时可以合并了
		span->_pageld = sprv->_pageld;
		span->_n += sprv->_n;
		_spanList[sprv->_n].Erase(sprv);
		//delete sprv;
		_spanPool.Delete(sprv);
	}

	while (true)
	{
		::span* cur = (::span*)_pageToSpan.get(span->_pageld - 1);
		if (cur == nullptr) break;

		// 此时找到上一个了，但是需要考虑是否在使用
		::span* snext = cur;
		if (snext->_isUse) break;

		if (snext->_n + span->_n > NPAGES - 1) break;  // 此时合成大小超过128page了，可以合并但是无法管理

		// 此时可以合并了
		span->_n += snext->_n;
		_spanList[snext->_n].Erase(snext);
		//delete snext;
		_spanPool.Delete(snext);
	}

	// 此时合并完成，我们也需要将这页号连入map，方便其他人回收内存进行一个连接
	_spanList[span->_n].HeadInsert(span);
	span->_isUse = false;  // 此时被回收了
	//pageToSpan[span->_pageld] = span;
	//pageToSpan[span->_pageld + span->_n - 1] = span;
	_pageToSpan.set(span->_pageld, span);
	_pageToSpan.set(span->_pageld + span->_n - 1, span);
}
